使用增强输入系统
在游戏开发中,处理用户输入是一个关键部分,尤其是当需要处理复杂的输入序列或快速反应事件(Quick Time Events,简称 QTE)时。Dora SSR 提供了一 个增强的输入系统,使开发者能够更加高效、灵活地管理各类输入事件。本教程将引导您了解如何设置和使用这个输入系统,并详细解释涉及的新概念。
1. 涉及的新概念
Dora SSR 的增强输入系统允许您创建复杂的输入逻辑,例如多阶段的 QTE、组合按键等。通过使用输入上下文、动作和触发器,您可以精确地控制游戏在不同状态下如何响应玩家的输入。
1.1 动作(Action)
动作是输入系统中的基本单元,定义了在特定条件下触发行为的一组定义。例如,按下确认键进行确认、按下移动键移动角色等。
1.2 输入上下文(Input Context)
输入上下文是包含一组动作的集合,允许您根据游戏场景激活或停用一组输入动作。例如,在游戏菜单的场景中,您可能只需要处理导航和选择的输入。在游戏内场景中,您可能需要处理另外一组不同的输入,如移动、攻击等。
1.3 触发器(Trigger)
触发器定义了动作被激活的条件,可以是简单的按键,也可以是复杂的输入序列。Dora SSR 提供了多种类型的触发器,包括:
- KeyDown:当所有指定的键被按下时触发。
- KeyUp:当所有指定的键被按下并且其中任何一个被释放时触发。
- KeyPressed:当所有指定的键正在被按下时触发。
- KeyHold:当特定键被按下并且保持按下指定的持续时间时触发。
- KeyTimed:当特定键在指定的时间窗口内被按下时触发。
- KeyDoubleDown:当特定键被双击时触发。
- AnyKeyPressed:当任何键被持续按下时触发。
- ButtonDown:当所有指定的游戏手柄按钮被按下时触发。
- ButtonUp:当所有指定的游戏手柄按钮被按下并且其中任何一个被释放时触发。
- ButtonPressed:当所有指定的游戏手柄按钮正在被按下时触发。
- ButtonHold:当特定的游戏手柄按钮被按下并且保持按下指定的持续时间后触发。
- ButtonTimed:当特定的游戏手柄按钮在指定的时间窗口内被按下时触发。
- ButtonDoubleDown:当特定的游戏手柄按钮被双击时触发。
- AnyButtonPressed:当任何游戏手柄按钮被持续按下时触发。
- JoyStick:当特定的游戏手柄轴被移动时触发。
- JoyStickThreshold:当操纵杆移动超过指定阈值时触发。
- JoyStickDirectional:当操纵杆在容忍的偏差角度内朝特定方向移动时触发。
- JoyStickRange:当操纵杆在指定范围内时触发。
- Sequence:要求触发器按特定顺序检测发生。
- Selector:只要有一个触发器激活,动作就会被触发。
- Block:阻止其他触发器的激活。
1.4 触发器状态(Trigger State)
当触发器被激活时,它会触发一个对应的引擎的全局事件,该事件包含触发器的当前状态。触发器状态有三种:
- Ongoing:触发器条件正在进行中。
- Completed:触发器条件已完成。
- Canceled:触发器条件被取消。
1.5 上下文、动作、触发器和触发器状态的关系
一组输入上下文包含多个动作,每个动作包含一个树形结构组织的触发器,触发器提供了多种触发事件源,并提供了当前的输入状态。
1.6 触发器的嵌套
下面是一个树形嵌套的触发器定义示例,用于描述一个同时按下键盘的 Ctrl
键和 C
键的触发器:
对应的触发器代码定义:
- Lua
- Teal
- TypeScript
- YueScript
Trigger.Sequence({
Trigger.KeyPressed("LCtrl"),
Trigger.KeyDown("C")
})
Trigger.Sequence({
Trigger.KeyPressed("LCtrl"),
Trigger.KeyDown("C")
})
Trigger.Sequence([
Trigger.KeyPressed(KeyName.LCtrl),
Trigger.KeyDown(KeyName.C)
])
Trigger.Sequence [
Trigger.KeyPressed "LCtrl"
Trigger.KeyDown "C"
]
下面是定义一个任意长按键盘 回车键
或是游戏控制器 A
按钮,并保持 1 秒后触发确认行为的触发器:
对应的触发器代码定义:
- Lua
- Teal
- TypeScript
- YueScript
Trigger.Selector({
Trigger.KeyHold("Return", 1),
Trigger.ButtonHold("a", 1)
})
Trigger.Selector({
Trigger.KeyHold("Return", 1),
Trigger.ButtonHold("a", 1)
})
Trigger.Selector([
Trigger.KeyHold(KeyName.Return, 1),
Trigger.ButtonHold(ButtonName.A, 1)
])
Trigger.Selector [
Trigger.KeyHold "Return", 1
Trigger.ButtonHold "a", 1
]
2. 创建输入系统
2.1 简单的输入系统一
下面是创建一个输入系统的简单代码示例:
- Lua
- Teal
- TypeScript
- YueScript
-- 引入模块
local InputManager <const> = require("InputManager")
local Trigger <const> = InputManager.Trigger
local Node <const> = require("Node")
-- 创建输入管理器,包含一个上下文和一个动作
local input = InputManager.CreateManager({
testContext = {
["Ctrl+C"] = Trigger.Sequence({
Trigger.KeyPressed("LCtrl"),
Trigger.KeyDown("C")
})
}
})
-- 创建一个节点,用于接收和处理输入事件
local node = Node()
-- 连接全局的事件信号,注意这里的 "Input." 后面的字符串是对应动作的名称
node:gslot("Input.Ctrl+C", function(state, progress, value)
if state == "Completed" then
print("Ctrl+C 触发完成")
-- 移除当前生效的上下文,按下 Ctrl+C 便不再触发
input:popContext()
end
end)
-- 激活上下文 testContext,使得包含的输入触发器生效
input:pushContext("testContext")
-- 引入模块
local InputManager <const> = require("InputManager")
local Trigger <const> = InputManager.Trigger
local Node <const> = require("Node")
local type Vec2 = require("Vec2")
-- 创建输入管理器,包含一个上下文和一个动作
local input = InputManager.CreateManager({
testContext= {
["Ctrl+C"] = Trigger.Sequence({
Trigger.KeyPressed("LCtrl"),
Trigger.KeyDown("C")
})
}
})
-- 创建一个节点,用于接收和处理输入事件
local node = Node()
-- 连接全局的事件信号,注意这里的 "Input." 后面的字符串是对应动作的名称
node:gslot("Input.Ctrl+C", function(state: InputManager.TriggerState, progress: number, value: number | boolean | Vec2.Type)
if state == "Completed" then
print("Ctrl+C 触发完成")
-- 移除当前生效的上下文,按下 Ctrl+C 便不再触发
input:popContext()
end
end)
-- 激活上下文 testContext,使得包含的输入触发器生效
input:pushContext("testContext")
import { Node, KeyName, Vec2 } from "Dora";
import { CreateManager, Trigger, TriggerState } from "InputManager";
// 创建输入管理器,包含一个上下文和一个动作
const inputManager = CreateManager({
testContext: {
["Ctrl+C"]: Trigger.Sequence([
Trigger.KeyPressed(KeyName.LCtrl),
Trigger.KeyDown(KeyName.C)
])
}
});
// 创建一个节点,用于接收和处理输入事件
const node = Node();
// 连接全局的事件信号,注意这里的 "Input." 后面的字符串是对应动作的名称
node.gslot("Input.Ctrl+C", (state: TriggerState, progress: number, value: number | boolean | Vec2.Type) => {
if (state === TriggerState.Completed) {
print("Ctrl+C 触发完成");
// 移除当前生效的上下文,按下 Ctrl+C 便不再触发
inputManager.popContext();
}
});
// 激活上下文 testContext,使得包含的输入触发器生效
inputManager.pushContext("testContext");
_ENV = Dora
import "InputManager" as :CreateManager, :Trigger
-- 创建输入管理器,包含一个上下文和一个动作
inputManager = CreateManager
testContext:
["Ctrl+C"]: Trigger.Sequence [
Trigger.KeyPressed "LCtrl"
Trigger.KeyDown "C"
]
-- 创建一个节点,用于接收和处理输入事件
with Node!
-- 连接全局的事件信号,注意这里的 "Input." 后面的字符串是对应动作的名称
\gslot "Input.Ctrl+C", (state, progress, value) ->
if state == "Completed"
print "Ctrl+C 触发完成"
-- 移除当前生效的上下文,按下 Ctrl+C 便不再触发
inputManager\popContext!
-- 激活上下文 testContext,使得包含的输入触发器生效
inputManager\pushContext "testContext"
在这个示例中,我们创建了一个输入管理器,定义了一个输入上下文和一个动作。动作 Ctrl+C
触发器定义了按下键盘的 Ctrl
键和 C
键的触发条件。我们将这个上下文推入输入管理器进行激活。然后创建一个场景节点,用于接收和处理输入事件。最后,我们连接了对应的全局的事件信号,当动作 Ctrl+C
完成时,打印一条消息并移除当前生效的上下文。